import dayjs from 'dayjs' 
import smCrypto from 'sm-crypto'
import { Base64 } from "js-base64"

// 字符集
const _charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789'

// SM2加密参数
const _cipherMode = 1  // 1 - C1C3C2，0 - C1C2C3，默认为1
const _publicKey =
  '04050af56e07eff69e3f2c7b4650309393c00c6c31c7ac800181067ae42be49e2455b7dfcf6716e3f76e53a9c4054c331c3998b20a2282a376a90cda5d20cd0767'

/**
 * Base64加密
 * @param {*} txtStr 待加密数据
 * @returns 加密后数据
 */
export const base64Encode = (txtStr:string) => {
  return Base64.encode(txtStr)
}

/**
 * Base64解密
 * @param {*} txtStr 待加密数据
 * @returns 加密后数据
 */
export const base64Decode = (txtStr:string) => {
  return Base64.decode(txtStr)
}

/**
 * SM2公钥加密（后端解密）
 * @param {*} txtStr 待加密数据
 * @returns 加密后数据
 */
export const sm2Encrypt = (txtStr:string) => {
  return smCrypto.sm2.doEncrypt(txtStr, _publicKey, _cipherMode)
}


/**
 * SM4秘钥加密
 * @param {*} txtStr 待加密数据
 * @param {*} keyStr 加密秘钥
 * @param {*} modeBase 加密模式
 * @returns 加密后数据
 */
export const sm4Encrypt = (txtStr:string, keyStr:string, modeBase:object) => {
  return smCrypto.sm4.encrypt(txtStr, keyStr, modeBase)
}


/**
 * SM4秘钥解密
 * @param {*} txtStr 待解密数据
 * @param {*} keyStr 解密秘钥
 * @param {*} modeBase 解密模式
 * @returns 解密后数据
 */
export const sm4Decrypt = (txtStr:string, keyStr:string, modeBase:string) => {
  return smCrypto.sm4.decrypt(txtStr, keyStr, modeBase)
}

/**
 * 安全验证加密，适用于请求基础验证，类似于验证码
 * @param {*} txtStr 请求接口地址
 * @returns 请求验证串
 */
export const securityEncrypt = (txtStr:string):string => {
	const prefix = "_B"
	const baseStr = "1357902468"
	const firstStr = txtStr.split('/').filter(part => part.length > 0).map(part => part[0]).join('').padEnd(4, '0')
	const secondStr = (dayjs().valueOf() % 10000).toString().padStart(4, '0')
	const calcuStr = secondStr.split('').reduce((a, b) => a + parseInt(b), 0)
	const modIndex = baseStr.indexOf((calcuStr % 10).toString())
  return prefix + modIndex + getIntervelValue(firstStr, secondStr)
}

/**
 * 获取两个字符串间隔插值
 * @param firstStr 第一个字符串
 * @param secondStr 第二个字符串
 * @returns 插值字符串
 */
const getIntervelValue = (firstStr:string, secondStr:string): string => {
	return Array.from({ length: secondStr.length }, (_, i) => 
    (i < firstStr.length ? firstStr[i] : '') + (i < secondStr.length ? secondStr[i] : '')
	).join('').toUpperCase()
}

/**
 * 请求数据加密，适用于数据提交
 * <p>
 * 1、通过SM4对称加密，对请求头、请求体进行加密
 * 2、通过SM2公钥加密，对秘钥等信息信息进行加密
 * 3、将加密后的请求头、请求体、秘钥信息、盐值、时间戳、签名拼接后返回
 * </p>
 * @param {*} header 请求头
 * @param {*} body 请求体
 * @returns 
 */
export const requestEncrypt = (header:any, body:any) => {
  //偏移量
  const iv = getRandomString(16)
  //加密秘钥
	const secretKey = stringToHex(getRandomString(16))
  //加密模式
  const secretMode = {iv:iv}
  //加密内容
	const content = JSON.stringify({"header":header, "body":body})
  //加密后数据
	const data = sm4Encrypt(content, secretKey, secretMode)
  //加密后秘钥
	const extra = sm2Encrypt(secretKey + iv)
  //盐值
	const salt = getRandomString(16)
  //时间戳
	const timestamp = new Date().getTime()
  //数据签名
	const signature = smCrypto.sm3(data+extra+salt+timestamp)
	return {
		"data":data,
		"extra":extra,
		"salt":salt,
		"timestamp":timestamp,
		"signature":signature
	}
}

/**
 * 获取随机字符串
 * @param length 长度
 * @returns 字符串
 */
const getRandomString = (length:number) => {
	let min = 0, max = _charStr.length - 1, randomStr = ''
	//判断是否指定长度，否则默认长度为15
	length = length || 15
	//循环生成字符串
	for (var i = 0, index; i < length; i++) {
		index = getRandomIndex(min, max, i)
		randomStr += _charStr[index]
	}
	return randomStr
}

/**
 * 获取随机数索引
 * @param min 最小值
 * @param max 最大值
 * @param i 第几位
 * @returns 索引
 */
const getRandomIndex = (min:number, max:number, i:number) => {
	let randomIndex = Math.floor(Math.random() * (max - min + 1) + min),
    numStart = _charStr.length - 10
	//如果字符串第一位是数字，则递归重新获取
	if (i == 0 && randomIndex >= numStart) {
		randomIndex = getRandomIndex(min, max, i)
	}
	return randomIndex
}


/**
 * 字符串转16进制
 * @param {*} str 待转字符串
 * @returns 16进制字符串
 */
const stringToHex = (str:string) => { 
	let hexString = ''
  for (let i = 0; i < str.length; i++) {
    let charCode = str.charCodeAt(i).toString(16)
    hexString += charCode.padStart(2, '0')
  }
  return hexString
 }

